package MyGui;

import java.awt.*;
import java.awt.event.*;
import java.io.*;
import javax.swing.*;
import javax.swing.border.TitledBorder;
import javax.swing.table.AbstractTableModel;
import java.text.*;

import DistSys.*;
import Element.AbsElem;
import Engines.*;

public class SensitivityDlg extends MyDlgFrame implements ActionListener{
    private double[][] indices, indices2;
    private double[] sens;
    private double[] buffer;
    private int idPara;
    private double dPerturbedZone;

    private final int NUM_IND =10;
    private final int MAIFI=0,SAIFI=1,SAIDI=2,CAIDI=3,ASAI=4,
                      CAIFI=5,ASIFI=6,ASIDI=7,CTAIDI=8,SARFI=9;

    private ZoneComboBox comboChoice;
    private JComboBox    comboPara;
    private JRadioButton rbRadial, rbNetwork;
    private ButtonGroup  btnGroup;

    private ZoneComboBox comboChoice2;
    private JButton      btnReCompute, btnComputeAllZones;

    private JList   lstTemplates;
    private JLabel  lblNumElem;
    private JButton btnDo;

    private AbstractTableModel modelResults;
    private JTable     tblResults;
    private String[]   resultsColumns = new String[] {
        "Index", "Value", "Sensitivity"
    };
    private String[][] resultsRows = new String[][] {
        { "MAIFI",  "", "" },
        { "SAIFI",  "", "" },
        { "SAIDI",  "", "" },
        { "CAIDI",  "", "" },
        { "ASAI",   "", "" },
        { "CAIFI",  "", "" },
        { "ASIFI",  "", "" },
        { "ASIDI",  "", "" },
        { "CTAIDI", "", "" },
        { "SARFI",  "", "" }
    };

    public SensitivityDlg(MainFrame parent, String title) {
        super(parent, title);
        try {
            GridBagConstraints c = new GridBagConstraints();
            c.insets =  new Insets(GAP, GAP, 0, 0);
            c.fill = GridBagConstraints.BOTH;
            c.anchor = GridBagConstraints.WEST;
            c.weightx = 1;

            Container pane = this.getContentPane();
            pane.setLayout(gridBagLayout);

            JPanel northWestPanel = createNorthWestPanel();
            JPanel northEastPanel = createNorthEastPanel();
            JPanel southWestPanel = createSouthWestPanel();
            JPanel southEastPanel = createSouthEastPanel();

            //The gap between East and West parts will be greater than
            //North-South parts because users are expected to read this form
            //top-down, not left - right.
            c.insets.right = 2* GAP;
            c.gridx=0; c.gridy=0; c.gridwidth =1; c.gridheight=1;
            pane.add(northWestPanel,c);

            c.insets.bottom = GAP;
            c.weighty = 1;
            c.gridx=0; c.gridy=1; c.gridwidth =1; c.gridheight=1;
            pane.add(southWestPanel,c);

            c.insets.left  = 2*GAP; c.insets.right = GAP; c.insets.bottom = 0;
            c.weighty = 0;
            c.gridx=1; c.gridy=0; c.gridwidth =1; c.gridheight=1;
            pane.add(northEastPanel,c);

            c.insets.bottom = GAP;
            c.weighty = 1;
            c.gridx=1; c.gridy=1; c.gridwidth =1; c.gridheight=1;
            pane.add(southEastPanel,c);

            this.setDefaultCloseOperation(JDialog.HIDE_ON_CLOSE);
            this.setSize(490, 430);
            this.setLocation(200,200);
            // this.setResizable(false);

            btnDo.addActionListener(this);
            btnReCompute.addActionListener(this);
            btnComputeAllZones.addActionListener(this);
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

    private JPanel createNorthWestPanel(){
        JPanel p = new JPanel();
        p.setBorder( new TitledBorder(" Components to Perturb "));

        comboChoice = new ZoneComboBox();
        comboChoice.setFont(GuiConst.fontDefault);
        comboPara = new JComboBox(SysConst.ssDataField); //not editable
        comboPara.setFont(GuiConst.fontDefault);

        JLabel lblChoice = new JLabel("Area");
        lblChoice.setDisplayedMnemonic('A');
        lblChoice.setLabelFor(comboChoice);
        JLabel lblPara = new JLabel("Para.");
        lblPara.setDisplayedMnemonic('P');
        lblPara.setLabelFor(comboPara);

        rbRadial = new JRadioButton("Radial Analysis");
        rbRadial.setFont(GuiConst.fontDefault);
        rbRadial.setMnemonic('R');
        rbNetwork= new JRadioButton("Network Analysis");
        rbNetwork.setFont(GuiConst.fontDefault);
        rbNetwork.setMnemonic('N');
        btnGroup = new ButtonGroup();
        btnGroup.add( rbRadial );
        btnGroup.add( rbNetwork );
        rbRadial.setSelected(true);

        GridBagConstraints c = new GridBagConstraints();
        c.insets = new Insets(0, GAP, GAP, 0);
        c.fill = GridBagConstraints.HORIZONTAL;
        c.anchor = GridBagConstraints.WEST;

        p.setLayout(gridBagLayout);

        c.gridx=0; c.gridy=0; c.weightx = 1; p.add(comboChoice,c);
        c.gridx=0; c.gridy=1; p.add(comboPara,c);

        c.insets.right = GAP;
        c.gridx=1; c.gridy=0; c.weightx = 0; p.add(lblChoice,c);
        c.gridx=1; c.gridy=1; p.add(lblPara,c);

        c.insets.bottom = 0;
        c.gridx=0; c.gridy=2; c.gridwidth=2; c.weightx = 1; p.add(rbRadial,c);
        c.gridx=0; c.gridy=3; p.add(rbNetwork,c);
        return p;
    }

    private JPanel createNorthEastPanel(){
        JPanel p = new JPanel();
        p.setBorder( new TitledBorder(" Reliability Indices "));

        comboChoice2 = new ZoneComboBox();
        JLabel lblChoice2 = new JLabel("Area");
        lblChoice2.setDisplayedMnemonic('E');
        lblChoice2.setLabelFor(comboChoice2);

        btnReCompute = new JButton("Recompute");
        btnReCompute.setMnemonic('C');
        btnComputeAllZones = new JButton("Export Results for All Zones");
        btnComputeAllZones.setMnemonic('Z');
        btnReCompute.setEnabled(false);
        btnComputeAllZones.setEnabled(false);

        GridBagConstraints c = new GridBagConstraints();
        c.insets =  new Insets(0, GAP, GAP, 0);
        c.fill = GridBagConstraints.HORIZONTAL;
        c.anchor = GridBagConstraints.WEST;

        p.setLayout(gridBagLayout);
        c.gridx=0; c.gridy=0; c.weightx = 1; p.add(comboChoice2,c);
        c.insets.right = GAP;
        c.gridx=1; c.gridy=0; c.weightx = 0; p.add(lblChoice2,c);
        c.gridwidth = 2;
        c.gridx=0; c.gridy=1; c.weightx = 1; p.add(btnReCompute,c);
        c.anchor = GridBagConstraints.NORTHWEST;
        c.gridx=0; c.gridy=2; c.weighty = 1; p.add(btnComputeAllZones,c);
        return p;
    }

    private JPanel createSouthWestPanel(){
        JPanel p = new JPanel(new BorderLayout(GAP, 0));

        lstTemplates = new JList( Templates.mapAllListToStringsWithAllNoneOptions() );
        lstTemplates.setFont(GuiConst.fontDefault);
        JScrollPane scrollPaneForTmplt = new JScrollPane(lstTemplates);
        scrollPaneForTmplt.setPreferredSize( new Dimension(170,150));

        JPanel panelTmplt = new JPanel(gridBagLayout);
        panelTmplt.setBorder(new TitledBorder(" Default Templates to Perturb "));
        GridBagConstraints c = new GridBagConstraints();
        c.insets =  new Insets(0, GAP, GAP, GAP);
        c.fill = GridBagConstraints.BOTH;
        c.weightx = c.weighty = 1;
        panelTmplt.add(scrollPaneForTmplt, c);

        btnDo = new JButton("Perform Sensitivity Analysis");
        btnDo.setFont(GuiConst.fontDefault);
        btnDo.setMnemonic('S');
        lblNumElem = new JLabel();
        lblNumElem.setFont(GuiConst.fontDefault);

        JPanel pSub = new JPanel(new GridLayout(2,1));
        pSub.add(lblNumElem);
        pSub.add(btnDo);

        p.add(panelTmplt, BorderLayout.CENTER);
        p.add(pSub, BorderLayout.SOUTH);
        return p;
    }

    private JPanel createSouthEastPanel(){
        JPanel p = new JPanel(gridBagLayout);
        p.setBorder(new TitledBorder(" Results "));

        modelResults = new AbstractTableModel() {
            public int getColumnCount() { return resultsColumns.length; }
            public int getRowCount() { return resultsRows.length; }
            public String getColumnName(int col) { return resultsColumns[col]; }
            public Object getValueAt(int row, int col) {
                return resultsRows[row][col];
            }
            public Class getColumnClass(int col) {
                return (col == 0 ? String.class : Number.class);
            }
        };

        tblResults = new JTable(modelResults);
        tblResults.setAutoResizeMode(JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS);
        tblResults.getColumnModel().getColumn(0).setPreferredWidth(60);
        tblResults.getColumnModel().getColumn(1).setPreferredWidth(100);
        tblResults.getColumnModel().getColumn(2).setPreferredWidth(100);
        tblResults.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        tblResults.setShowGrid(false);

        JScrollPane scrollResults = new JScrollPane(tblResults);
        scrollResults.setPreferredSize(new Dimension(200,150));

        GridBagConstraints c = new GridBagConstraints();
        c.insets =  new Insets(0, GAP, GAP, GAP);
        c.fill = GridBagConstraints.BOTH;
        c.weightx = c.weighty = 1;
        p.add(scrollResults, c);

        return p;
    }

    public void actionPerformed(ActionEvent evt){
        Object o = evt.getSource();
        if( o == btnDo ){
            int numZones = Sys.prjInfo.allZone.size();
            indices = new double[numZones+3][10];
            indices2= new double[numZones+3][10];
            sens = new double[10];
            buffer = new double[Sys.elemList.size()];

            try {
                dPerturbedZone = comboChoice.getSelectedZone();
            } catch(ZoneComboBox.ZoneException e) {
                JOptionPane.showMessageDialog(this,
                  "'" + e.getMessage() + "' is not a valid zone.");
                return;
            }

            idPara = comboPara.getSelectedIndex();
            int idData = SysConst.mapDataIndexFromDisplayToValue(idPara);
            if (!SysConst.isDataPerturbable( idData ) ) {
                JOptionPane.showMessageDialog(this,
                    "Not a perturbable parameter.");
                return;
            }

            int[] selectedTemp = lstTemplates.getSelectedIndices();
            int szTemp = selectedTemp.length;
            if( szTemp == 0 || (szTemp > 0 && selectedTemp[0] == 1 ) ){
                JOptionPane.showMessageDialog(this, "No templates selected." +
                    "You must selected one or more templates to purturb");
                return;
            }
            Templates.clearSelection();
            Templates.setSelection( selectedTemp);

            SysConst.progress = 0;
            UpdateProgressThread t = new UpdateProgressThread(
                mainFrame.btnPanel, ButtonPanel.ENGN_MODE);
            t.start();

            btnReCompute.setEnabled( false );
            btnComputeAllZones.setEnabled( false );

            // Set passed zone to the Zone for Root Cause Analysis
            EngineProcessor.dZoneToEngine = EngineProcessor.dRootCauseZone;

            String urlStr;
            if( rbRadial.isSelected()){ //for radial system
                urlStr = WebConst.getDistrelURL() +
                    "?WebPath=" + WebConst.getEngineWebPath() +
                    "&PadPath=" + Sys.personalInfo.getTempPath().getPath() +
                    "&Mode=1&Zone=" + EngineProcessor.getZoneAsEngineParamOld() +
                    "&ID=0&P1=0&Blocksag=1" +
                    "&TargetMAIFI=0&TargetSAIFI=0&TargetSAIDI=0";
                mainFrame.menuBar.engineThread = new EngineThread(mainFrame,
                    this, urlStr, EngineThread.ENG_SENS_RAD);
            } else {//network system
                int iPowerFlowBlocked=0, iDoubleContingencies=0, iModelReduction=0;
                if( EngineProcessor.bDisableCapFlow ) { iPowerFlowBlocked=1; }
                if( EngineProcessor.bDisable2ndOrder) { iDoubleContingencies=1; }
                if( EngineProcessor.bUseNetReduction) { iModelReduction=1; }

                urlStr = WebConst.getNetrelURL() +
                    "?WebPath=" + WebConst.getEngineWebPath() +
                    "&PadPath=" + Sys.personalInfo.getTempPath().getPath() +
                    "&PowerFlowBlocked=" + iPowerFlowBlocked +
                    "&DoubleContingenciesBlocked=" + iDoubleContingencies +
                    "&Mode=1&Zone=" + EngineProcessor.getZoneAsEngineParamOld() +
                    "&ModelReduction=" + iModelReduction;
                mainFrame.menuBar.engineThread = new EngineThread(mainFrame,
                    this, urlStr, EngineThread.ENG_SENS_NET);
            }

            mainFrame.menuBar.engineThread.start();
        } else if( o == this.btnReCompute ){
            computeAndUpdateSensitivity();
        } else if( o == this.btnComputeAllZones ){
            try{
                File f = Sys.personalInfo.createTempExportFile();
                writeToFile(f);
                Sys.personalInfo.execExcel(f.getPath());
            } catch(IOException e) {
                JOptionPane.showMessageDialog(this,
                    "Error occured when writing to PA_Export.cvs\n"+e);
                return;
            } catch(Exception e) {
                JOptionPane.showMessageDialog(this, e);
                return;
            }
        }
    }//end of actionPerformed()

    private int mapZone2ID(double dZone) {
        int numZones = Sys.prjInfo.allZone.size();
        if (dZone == Zone.ENTIRE_SYSTEM)
            return numZones;
        else if (dZone == Zone.STUDY_AREA)
            return numZones+1;
        else if (dZone == Zone.SELECTED_AREA)
            return numZones+2;
        else {
            for (int i = 0; i < numZones; i++) {
                Zone z = (Zone)Sys.prjInfo.allZone.elementAt(i);
                if (dZone == z.getZoneNumber()) {
                    return i;
                }
            }
        }
        // This should not happen!
        return 0;
    }

    public void computeAndUpdateSensitivity() {
        try {
            double dZone2 = comboChoice2.getSelectedZone();
            computeSensitivity(dZone2);
            updateSensitityText(dZone2);
        } catch(ZoneComboBox.ZoneException e) {
            JOptionPane.showMessageDialog(this,
              "'" + e.getMessage() + "' is not a valid zone.");
        }
    }

    private void computeSensitivity(double dZone){
        int id = mapZone2ID(dZone);

        double d = Double.MIN_VALUE;
        for (int k = 0; k < 10; k++){
            if (Math.abs(indices[id][k]) < d) {
                if (Math.abs(indices2[id][k]) < d) {
                    sens[k] = 0;
                } else {
                    if (indices2[id][k] > 0) {
                        sens[k] = Double.POSITIVE_INFINITY;
                    } else {
                        sens[k] = Double.NEGATIVE_INFINITY;
                    }
                }
            } else {
                sens[k] = (indices2[id][k] - indices[id][k])/(indices[id][k]);
                sens[k] = sens[k]*100.0/0.05;
            }
        }
    }

    private void updateSensitityText(double dZone) {
        NumberFormat nf = NumberFormat.getNumberInstance();
        nf.setMaximumFractionDigits(2);
        nf.setGroupingUsed(false);

        int id = mapZone2ID(dZone);

        for (int k = 0; k < 10; k++) {
            resultsRows[k][1] = nf.format(indices[id][k]);
            if (Double.isInfinite(sens[k])) {
                resultsRows[k][2] = "Infinite";
            } else {
                resultsRows[k][2] = nf.format(sens[k]) + "%";
            }
        }
        this.modelResults.fireTableDataChanged();
    }

    public void calcIndicesForBaseCase(){
        int numZones = Sys.prjInfo.allZone.size();
        for (int i = 0; i < numZones; i++) {
            double dZone = ((Zone)Sys.prjInfo.allZone.elementAt(i)).getZoneNumber();
            EngineProcessor.computeReliabilityIndices(dZone);
            System.arraycopy(EngineProcessor.indices, 0, indices[i], 0, 10);
        }
        EngineProcessor.computeReliabilityIndices(Zone.ENTIRE_SYSTEM);
        System.arraycopy(EngineProcessor.indices, 0, indices[numZones], 0, 10);
        EngineProcessor.computeReliabilityIndices(Zone.STUDY_AREA);
        System.arraycopy(EngineProcessor.indices, 0, indices[numZones+1], 0, 10);
        EngineProcessor.computeReliabilityIndices(Zone.SELECTED_AREA);
        System.arraycopy(EngineProcessor.indices, 0, indices[numZones+2], 0, 10);
    }

    public void calcIndicesForPerturbedCase(){
        int numZones = Sys.prjInfo.allZone.size();
        for (int i = 0; i < numZones; i++) {
            double dZone = ((Zone)Sys.prjInfo.allZone.elementAt(i)).getZoneNumber();
            EngineProcessor.computeReliabilityIndices(dZone);
            System.arraycopy(EngineProcessor.indices, 0, indices2[i], 0, 10);
        }
        EngineProcessor.computeReliabilityIndices(Zone.ENTIRE_SYSTEM);
        System.arraycopy(EngineProcessor.indices, 0, indices2[numZones], 0, 10);
        EngineProcessor.computeReliabilityIndices(Zone.STUDY_AREA);
        System.arraycopy(EngineProcessor.indices, 0, indices2[numZones+1], 0, 10);
        EngineProcessor.computeReliabilityIndices(Zone.SELECTED_AREA);
        System.arraycopy(EngineProcessor.indices, 0, indices2[numZones+2], 0, 10);
    }

    public int perturbZone(){
        final double factor = 1.05; //factor to perturb

        //SysConst.MappedIndex mappedIndex = SysConst.mapData(idPara);
        int iIndex = SysConst.mapDataIndexFromDisplayToValue( idPara);
        if (iIndex <0 || iIndex >= AbsElem.NUM_DATA_FIELD ){ //!mappedIndex.isValid()) {
            JOptionPane.showMessageDialog(this,
                "No match for perturbed parameter.");
            return 0;
        }

        int cntr = 0; //counter
        try {
            for (int i = 0; i < Sys.elemList.size(); i++) {
                AbsElem elem = (AbsElem) Sys.elemList.elementAt(i);

                buffer[i] = elem.getData( iIndex );

                if (!Zone.getZoneFits(dPerturbedZone, elem))
                    continue;

                AbsElem dftTemp = elem.getDftTemp();
                if (dftTemp == null || !dftTemp.getElemGeomObj().isSelected)
                    continue;

                elem.multiplyData(iIndex, factor);

                cntr++;
            }//end of for
        } catch(IllegalArgumentException e) {
            JOptionPane.showMessageDialog(this,
                "Error while perturbing components: " + e.getMessage());
        }

        if (cntr == 1) { lblNumElem.setText("1 Component Perturbed"); }
        else { lblNumElem.setText(cntr + " Components Perturbed"); }
        return cntr;
    }//end of perturbZone()

    public void restorePara(){
        //SysConst.MappedIndex mappedIndex = SysConst.mapData(idPara);
        int iIndex = SysConst.mapDataIndexFromDisplayToValue( idPara);
        if ( iIndex<0 || iIndex>=AbsElem.NUM_DATA_FIELD) {
            JOptionPane.showMessageDialog(this, "An error occured. " +
                "Can't restore value after sensitivity analyisis.");
            return;
        }
        for (int i = 0; i < Sys.elemList.size(); i++) {
            AbsElem elem = (AbsElem)Sys.elemList.elementAt(i);
            elem.setData( iIndex, buffer[i]);
        }
    }//end of restorepara()

    public void completed(boolean stopNow) {
        if (!stopNow){
            btnReCompute.setEnabled(true);
            btnComputeAllZones.setEnabled(true);
            modelResults.fireTableDataChanged();
        }
    }

    private void writeToFile(File f) throws IOException{
        BufferedWriter bw = new BufferedWriter(new FileWriter(f));
        bw.write("\"Performance Advantange Sensitivity Analysis\"\n");
        bw.write("\"\"\n");
        bw.write("\"\"\n");
        bw.write("\"Zone\",\"MAIFI\",\"SAIFI\",\"SAIDI\",\"CAIDI\",\"SARFI\","
                +"\"dMAIFI\",\"dSAIFI\",\"dSAIDI\",\"dCAIDI\",\"dSARFI\""+"\n");

        NumberFormat nf = NumberFormat.getNumberInstance();
        nf.setMaximumFractionDigits(2);
        nf.setGroupingUsed(false);
        double dZone;
        int numZones = Sys.prjInfo.allZone.size();
        Zone z = null;
        for (int i = 0; i <= numZones; i++) {
            if (i < numZones) {
                z = (Zone)Sys.prjInfo.allZone.elementAt(i);
                dZone = z.getZoneNumber();
            } else { // final run for system
                dZone = Zone.ENTIRE_SYSTEM;
            }

            computeSensitivity(dZone);

            if (dZone != Zone.ENTIRE_SYSTEM) {
                bw.write("\""+z.getZoneString()+"\"");
            } else {
                bw.write("\"Total\"");
            }

            bw.write(","+nf.format( indices[i][MAIFI])
                    +","+nf.format( indices[i][SAIFI])
                    +","+nf.format( indices[i][SAIDI])
                    +","+nf.format( indices[i][CAIDI])
                    +","+nf.format( indices[i][SARFI])
                    +","+nf.format( sens[MAIFI])
                    +","+nf.format( sens[SAIFI])
                    +","+nf.format( sens[SAIDI])
                    +","+nf.format( sens[CAIDI])
                    +","+nf.format( sens[SARFI])+"\n");
        }//end of loop for zones
        bw.close();
    }//end of writeToFile()
}
